
namespace createjs {

	export class WebGLInspector extends EventDispatcher {
		static getRenderBufferTexture(arg0: any, arg1: any): any {
			throw new Error("Method not implemented.");
		}
		static _renderBatch_(): any {
			throw new Error("Method not implemented.");
		}
		static _viewportWidth(arg0: any, arg1: any, arg2: any, arg3: any): any {
			throw new Error("Method not implemented.");
		}
		static _viewportHeight(arg0: any, arg1: any, arg2: any, arg3: any): any {
			throw new Error("Method not implemented.");
		}

		static alternateOutput = undefined;

		/**
		 * Default stage to assume when non provided
		 * @type {StageGL}
		 * @private
		 */
		static stage = undefined;
		static _drawID: string;
		static _batchID: any;
		static batchReason: string;
		static _batchVertexCount: number;
		static _webGLContext: any;
		static _projectionMatrix: any;
		static _activeShader: any;
		static _vertices: any;
		static _indices: any;
		static _uvs: any;
		static _alphas: any;
		static _inspectorFrame: any;
		static _mainShader: any;
		static _batchTextureCount: number;
		static _batchTextureOutput: any;
		static _builtShaders: any;
		static _renderMode: any;

		// public methods:
		/**
		 * Utility to call the right logging
		 * @params *
		 */
		static log(...args);
		static log() {
			(WebGLInspector.alternateOutput ? WebGLInspector.alternateOutput.log : console.log).apply(this, arguments);
		};

		/**
		 * Perform all of the logging reports at once.
		 * @method log
		 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
		 */
		static logAll(stage) {
			if (!stage) { stage = WebGLInspector.stage; }

			WebGLInspector.log("Batches Per Draw", (stage._batchID / stage._drawID).toFixed(4));
			WebGLInspector.logContextInfo(stage._webGLContext);
			WebGLInspector.logDepth(stage.children, "");
			WebGLInspector.logTextureFill(stage);
		};

		/**
		 * Replace the stage's Draw command with a new draw command. This is useful for:
		 * <ul>
		 *     <li> Testing performance, with no render cost. See `WebGLInspector.drawEmpty` </li>
		 *     <li> Troubleshooting and tracking loaded textures. See `WebGLInspector.drawTexOnBuffer` </li>
		 *     <li> Misc feature or troubleshooting injection </li>
		 * </ul>
		 * @method replaceRenderBatchCall
		 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
		 * @param {Function} newFunc .
		 */
		static replaceRenderBatchCall(stage, newFunc) {
			if (!stage) { stage = WebGLInspector.stage; }

			if (newFunc === undefined && stage._renderBatch_) {
				stage._renderBatch = stage._renderBatch_;
				stage._renderBatch_ = undefined;
			} else {
				if (stage._renderBatch_ === undefined) {
					stage._renderBatch_ = stage._renderBatch;
				}
				stage._renderBatch = newFunc;
			}
		};

		/**
		 * Identical to replaceRenderBatchCall, but affects the Cover command.
		 * @method replaceRenderCoverCall
		 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
		 * @param {Function} newFunc .
		 */
		static replaceRenderCoverCall(stage, newFunc) {
			if (!stage) { stage = WebGLInspector.stage; }

			if (newFunc === undefined && stage._renderCover_) {
				stage._renderCover = stage._renderCover_;
				stage._renderCover_ = undefined;
			} else {
				if (stage._renderCover_ === undefined) {
					stage._renderCover_ = stage._renderCover;
				}
				stage._renderCover = newFunc;
			}
		};

		/**
		 * Recursively walk the entire display tree, log the attached items, and display it in a tree view.
		 * @method logDepth
		 * @param {Array} [children=WebGLInspector.stage.children] The children array to walk through.
		 * @param {String} prepend What to prepend to this output from this point onwards.
		 * @param {Function} customLog Which logging function to use, mainly for filtering or formatting output.
		 * Fallback hierarchy is customLog -> alternateOutput -> console.log.
		 */
		static logDepth(children, prepend, customLog?) {
			if (!children) { children = WebGLInspector.stage.children; }
			if (!prepend) { prepend = ""; }

			var l = children.length;
			for (var i = 0; i < l; i++) {
				var child = children[i];
				(customLog !== undefined ? customLog : WebGLInspector.log)(prepend + "-", child);
				if (child.children && child.children.length) {
					WebGLInspector.logDepth(child.children, "|" + prepend, customLog);
				}
			}
		};

		/**
		 * Examine the context and provide information about its capabilities.
		 * @method logContextInfo
		 * @param {WebGLRenderingContext} gl The WebGL context to inspect.
		 */
		static logContextInfo(gl) {
			if (!gl) { gl = WebGLInspector.stage._webGLContext; }
			var data = "== LOG:\n";
			data += "Max textures per draw: " + gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) + "\n";
			data += "Max textures active: " + gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS) + "\n";
			data += "\n";
			data += "Max texture size: " + (gl.getParameter(gl.MAX_TEXTURE_SIZE) / 2) + "^2 \n";
			data += "Max cache size: " + (gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) / 2) + "^2 \n";
			data += "\n";
			data += "Max attributes per vertex: " + gl.getParameter(gl.MAX_VERTEX_ATTRIBS) + "\n";
			data += "WebGL Version string: " + gl.getParameter(gl.VERSION) + "\n";
			data += "======";
			WebGLInspector.log(data);
		};

		/**
		 * Simulate renders and watch what happens for textures moving around between draw calls. A texture moving between
		 * slots means it was removed and then re-added to draw calls. Performance may be better if it was allowed to stay
		 * on GPU, consider sprite sheeting it with something stable.
		 * @method logTextureFill
		 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
		 */
		static logTextureFill(stage) {
			if (!stage) { stage = WebGLInspector.stage; }

			var dict = stage._textureDictionary;
			var count = stage._batchTextureCount;
			WebGLInspector.log("textureMax:", count);
			var output = [];
			for (var n in dict) {
				var str = n.replace(window.location.origin, "");
				var tex = dict[n];
				var shifted = tex._lastActiveIndex ? tex._lastActiveIndex === tex._activeIndex : false;
				output.push({ src: str, element: tex, shifted: shifted });
				tex._lastActiveIndex = tex._activeIndex;
			}

			output.sort(function (a, b) {
				if (a.element._drawID === stage._drawID) { return 1; }
				if (a.element._drawID < b.element._drawID) { return -1; }
				return 0;
			});

			var l = output.length;
			for (var i = 0; i < l; i++) {
				var out = output[i];
				var active = out.element._drawID === stage._drawID;
				WebGLInspector.log("[" + out.src + "] " + (active ? "ACTIVE" : "stale") + " " + (out.shifted ? "steady" : "DRIFT"), out.element);
			}
		};

		// protected methods:

		// utility methods:
		/**
		 * Utility function for use with {{#crossLink "logDepth"))((/crossLink}}. Logs an item's position and registration.
		 * Useful to see if something is being forced off screen or has an integer position.
		 * @method dispProps
		 * @param {String} prepend The string to show before the item, usually formatting for a tree view.
		 * @param {DisplayObject} item The item we're currently logging about.
		 * @static
		 */
		static dispProps(prepend, item) {
			if (!prepend) { prepend = ""; }

			var p = "\tP:" + item.x.toFixed(2) + "x" + item.y.toFixed(2) + "\t";
			var r = "\tR:" + item.regX.toFixed(2) + "x" + item.regY.toFixed(2) + "\t";

			WebGLInspector.log(prepend, item.toString() + "\t", p, r);
		};

		/**
		 * Utility function for use with {{#crossLink "replaceRenderBatchCall"))((/crossLink}}.
		 * Performs no GL draw command.
		 */
		static drawEmptyBatch() {
			WebGLInspector.log("BlankBatch[" + this._drawID + ":" + this._batchID + "] : " + this.batchReason);
			this._batchVertexCount = 0;
			this._batchID++;
		};

		/**
		 * Utility function for use with {{#crossLink "replaceRenderCoverCall"))((/crossLink}}.
		 * Performs no GL draw command.
		 */
		static drawEmptyCover() {
			WebGLInspector.log("BlankCover[" + this._drawID + ":" + this._batchID + "] : " + this.batchReason);
			this._batchID++;
		};

		/**
		 * Utility function for use with {{#crossLink "replaceRenderBatchCall"))((/crossLink}}.
		 */
		static drawTexBuffer() {
			var gl = this._webGLContext;
			var texSize = 2048;

			// backup
			var batchVertexCount = this._batchVertexCount;
			var projectionMatrix = this._projectionMatrix;
			var shader = this._activeShader;
			var vertices = this._vertices;
			var indices = this._indices;
			var uvs = this._uvs;
			var alphas = this._alphas;
			var reason = this.batchReason;

			// create
			if (this._inspectorFrame === undefined) {
				this._inspectorFrame = this.getRenderBufferTexture(texSize, texSize);
			} else {
				gl.bindFramebuffer(gl.FRAMEBUFFER, this._inspectorFrame._frameBuffer);
				gl.clear(gl.COLOR_BUFFER_BIT);
			}

			// configure
			this._activeShader = this._mainShader;
			gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
			gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
			gl.viewport(0, 0, texSize, texSize);

			this._projectionMatrix = new Float32Array([2 / texSize, 0, 0, 0, 0, -2 / texSize, 0, 0, 0, 0, 1, 0, -1, 1, 0, 1]);
			this._vertices = new Float32Array(this._batchTextureCount * 2 * createjs.StageGL.INDICIES_PER_CARD);
			this._indices = new Float32Array(this._batchTextureCount * 1 * createjs.StageGL.INDICIES_PER_CARD);
			this._uvs = new Float32Array(this._batchTextureCount * 2 * createjs.StageGL.INDICIES_PER_CARD);
			this._alphas = new Float32Array(this._batchTextureCount * 1 * createjs.StageGL.INDICIES_PER_CARD);
			this.batchReason = "LoadedTextureDebug";

			var squareBase = Math.ceil(Math.sqrt(this._batchTextureCount));
			for (var i = 0; i < this._batchTextureCount; i++) {
				var i1 = i * 6, i2 = i1 * 2;
				var row = i % squareBase, col = Math.floor(i / squareBase), size = (1 / squareBase) * texSize;
				this._vertices[i2] = (row) * size; this._vertices[i2 + 1] = (col) * size;
				this._vertices[i2 + 2] = (row) * size; this._vertices[i2 + 3] = (col + 1) * size;
				this._vertices[i2 + 4] = (row + 1) * size; this._vertices[i2 + 5] = (col) * size;
				this._vertices[i2 + 6] = this._vertices[i2 + 2]; this._vertices[i2 + 7] = this._vertices[i2 + 3];
				this._vertices[i2 + 8] = this._vertices[i2 + 4]; this._vertices[i2 + 9] = this._vertices[i2 + 5];
				this._vertices[i2 + 10] = (row + 1) * size; this._vertices[i2 + 11] = (col + 1) * size;
				this._uvs[i2] = 0; this._uvs[i2 + 1] = 1;
				this._uvs[i2 + 2] = 0; this._uvs[i2 + 3] = 0;
				this._uvs[i2 + 4] = 1; this._uvs[i2 + 5] = 1;
				this._uvs[i2 + 6] = 0; this._uvs[i2 + 7] = 0;
				this._uvs[i2 + 8] = 1; this._uvs[i2 + 9] = 1;
				this._uvs[i2 + 10] = 1; this._uvs[i2 + 11] = 0;
				this._indices[i1] = this._indices[i1 + 1] = this._indices[i1 + 2] = this._indices[i1 + 3] = this._indices[i1 + 4] = this._indices[i1 + 5] = i;
				this._alphas[i1] = this._alphas[i1 + 1] = this._alphas[i1 + 2] = this._alphas[i1 + 3] = this._alphas[i1 + 4] = this._alphas[i1 + 5] = 1;
			}

			// output
			this._batchVertexCount = this._batchTextureCount * createjs.StageGL.INDICIES_PER_CARD;
			this._renderBatch_();
			this._batchID--;

			// reset and perform
			gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer);

			var shaderData = this._builtShaders[this._renderMode];
			gl.blendEquationSeparate(shaderData.eqRGB, shaderData.eqA);
			gl.blendFuncSeparate(shaderData.srcRGB, shaderData.dstRGB, shaderData.srcA, shaderData.dstA);
			gl.viewport(0, 0, this._viewportWidth, this._viewportHeight);

			this._activeShader = shader;
			this._batchVertexCount = batchVertexCount;
			this._projectionMatrix = projectionMatrix;
			this._vertices = vertices;
			this._indices = indices;
			this._uvs = uvs;
			this._alphas = alphas;
			this.batchReason = reason;

			this._renderBatch_();
		};

	}

}

